package com.executor.gateway.controller;

import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.redis.core.ReactiveRedisTemplate;
import org.springframework.http.HttpStatus;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
import reactor.core.publisher.Mono;
import reactor.core.scheduler.Schedulers;

import javax.sql.DataSource;
import java.sql.Connection;
import java.time.Duration;
import java.time.LocalDateTime;
import java.util.HashMap;
import java.util.Map;

/**
 * 增强的响应式健康检查控制器
 * 提供详细的系统健康状态检查
 * 
 * @author miaoguoxin
 * @date 2024/01/01
 */
@RestController
@RequestMapping("/actuator")
@Slf4j
public class EnhancedHealthController {

    @Autowired(required = false)
    private ReactiveRedisTemplate<String, String> redisTemplate;

    @Autowired(required = false)
    private DataSource dataSource;

    /**
     * 基础健康检查
     */
    @GetMapping("/health")
    public Mono<Map<String, Object>> health() {
        Map<String, Object> health = new HashMap<>();
        health.put("timestamp", LocalDateTime.now().toString());
        health.put("application", "executor-api-gateway");
        health.put("version", "1.0.0");
        
        return Mono.zip(
                checkRedisHealth(),
                checkDatabaseHealth(),
                checkMemoryHealth()
        ).map(tuple -> {
            Map<String, Object> redisHealth = tuple.getT1();
            Map<String, Object> dbHealth = tuple.getT2();
            Map<String, Object> memoryHealth = tuple.getT3();
            
            boolean isHealthy = "UP".equals(redisHealth.get("status")) &&
                               "UP".equals(dbHealth.get("status")) &&
                               "UP".equals(memoryHealth.get("status"));
            
            health.put("status", isHealthy ? "UP" : "DOWN");
            health.put("redis", redisHealth);
            health.put("database", dbHealth);
            health.put("memory", memoryHealth);
            
            return health;
        }).onErrorReturn(createErrorHealth("Health check failed"));
    }

    /**
     * 详细健康检查
     */
    @GetMapping("/health/detailed")
    public Mono<Map<String, Object>> detailedHealth() {
        Map<String, Object> health = new HashMap<>();
        health.put("timestamp", LocalDateTime.now().toString());
        health.put("application", "executor-api-gateway");
        health.put("version", "1.0.0");
        
        return Mono.zip(
                checkRedisHealth(),
                checkDatabaseHealth(),
                checkMemoryHealth(),
                checkDiskHealth(),
                checkSystemHealth()
        ).map(tuple -> {
            Map<String, Object> components = new HashMap<>();
            components.put("redis", tuple.getT1());
            components.put("database", tuple.getT2());
            components.put("memory", tuple.getT3());
            components.put("disk", tuple.getT4());
            components.put("system", tuple.getT5());
            
            boolean isHealthy = components.values().stream()
                    .allMatch(component -> "UP".equals(((Map<?, ?>) component).get("status")));
            
            health.put("status", isHealthy ? "UP" : "DOWN");
            health.put("components", components);
            
            return health;
        }).onErrorReturn(createErrorHealth("Detailed health check failed"));
    }

    /**
     * 检查Redis健康状态
     */
    private Mono<Map<String, Object>> checkRedisHealth() {
        if (redisTemplate == null) {
            Map<String, Object> redisHealth = new HashMap<>();
            redisHealth.put("status", "DOWN");
            redisHealth.put("message", "Redis template not configured");
            return Mono.just(redisHealth);
        }
        
        return redisTemplate.opsForValue().set("health:check", "ok")
                .then(redisTemplate.expire("health:check", Duration.ofSeconds(10)))
                .then(redisTemplate.opsForValue().get("health:check"))
                .map(result -> {
                    Map<String, Object> redisHealth = new HashMap<>();
                    if ("ok".equals(result)) {
                        redisHealth.put("status", "UP");
                        redisHealth.put("message", "Redis connection is healthy");
                    } else {
                        redisHealth.put("status", "DOWN");
                        redisHealth.put("message", "Redis connection test failed");
                    }
                    return redisHealth;
                })
                .onErrorReturn(createComponentErrorHealth("Redis connection failed"));
    }

    /**
     * 检查数据库健康状态
     */
    private Mono<Map<String, Object>> checkDatabaseHealth() {
        if (dataSource == null) {
            Map<String, Object> dbHealth = new HashMap<>();
            dbHealth.put("status", "DOWN");
            dbHealth.put("message", "DataSource not configured");
            return Mono.just(dbHealth);
        }
        
        return Mono.fromCallable(() -> {
            Map<String, Object> dbHealth = new HashMap<>();
            try (Connection connection = dataSource.getConnection()) {
                boolean isValid = connection.isValid(5);
                if (isValid) {
                    dbHealth.put("status", "UP");
                    dbHealth.put("message", "Database connection is healthy");
                    dbHealth.put("database", connection.getMetaData().getDatabaseProductName());
                } else {
                    dbHealth.put("status", "DOWN");
                    dbHealth.put("message", "Database connection is invalid");
                }
            } catch (Exception e) {
                dbHealth.put("status", "DOWN");
                dbHealth.put("message", "Database connection failed: " + e.getMessage());
                log.error("Database health check failed", e);
            }
            return dbHealth;
        }).subscribeOn(Schedulers.boundedElastic())
        .onErrorReturn(createComponentErrorHealth("Database health check error"));
    }

    /**
     * 检查内存健康状态
     */
    private Mono<Map<String, Object>> checkMemoryHealth() {
        return Mono.fromCallable(() -> {
            Map<String, Object> memoryHealth = new HashMap<>();
            Runtime runtime = Runtime.getRuntime();
            
            long totalMemory = runtime.totalMemory();
            long freeMemory = runtime.freeMemory();
            long usedMemory = totalMemory - freeMemory;
            long maxMemory = runtime.maxMemory();
            
            double usagePercentage = (double) usedMemory / maxMemory * 100;
            
            memoryHealth.put("status", usagePercentage < 90 ? "UP" : "DOWN");
            memoryHealth.put("total", formatBytes(totalMemory));
            memoryHealth.put("free", formatBytes(freeMemory));
            memoryHealth.put("used", formatBytes(usedMemory));
            memoryHealth.put("max", formatBytes(maxMemory));
            memoryHealth.put("usage_percentage", String.format("%.2f%%", usagePercentage));
            
            return memoryHealth;
        }).subscribeOn(Schedulers.boundedElastic());
    }

    /**
     * 检查磁盘健康状态
     */
    private Mono<Map<String, Object>> checkDiskHealth() {
        return Mono.fromCallable(() -> {
            Map<String, Object> diskHealth = new HashMap<>();
            try {
                java.io.File root = new java.io.File("/");
                long totalSpace = root.getTotalSpace();
                long freeSpace = root.getFreeSpace();
                long usedSpace = totalSpace - freeSpace;
                
                double usagePercentage = (double) usedSpace / totalSpace * 100;
                
                diskHealth.put("status", usagePercentage < 90 ? "UP" : "DOWN");
                diskHealth.put("total", formatBytes(totalSpace));
                diskHealth.put("free", formatBytes(freeSpace));
                diskHealth.put("used", formatBytes(usedSpace));
                diskHealth.put("usage_percentage", String.format("%.2f%%", usagePercentage));
            } catch (Exception e) {
                diskHealth.put("status", "DOWN");
                diskHealth.put("message", "Disk space check failed: " + e.getMessage());
                log.error("Disk health check failed", e);
            }
            return diskHealth;
        }).subscribeOn(Schedulers.boundedElastic());
    }

    /**
     * 检查系统健康状态
     */
    private Mono<Map<String, Object>> checkSystemHealth() {
        return Mono.fromCallable(() -> {
            Map<String, Object> systemHealth = new HashMap<>();
            
            systemHealth.put("status", "UP");
            systemHealth.put("java_version", System.getProperty("java.version"));
            systemHealth.put("os_name", System.getProperty("os.name"));
            systemHealth.put("os_version", System.getProperty("os.version"));
            systemHealth.put("processors", Runtime.getRuntime().availableProcessors());
            systemHealth.put("uptime", getUptime());
            
            return systemHealth;
        }).subscribeOn(Schedulers.boundedElastic());
    }

    /**
     * 就绪状态检查
     */
    @GetMapping("/health/readiness")
    public Mono<Map<String, Object>> readiness() {
        return Mono.zip(
                checkRedisHealth(),
                checkDatabaseHealth()
        ).map(tuple -> {
            Map<String, Object> readiness = new HashMap<>();
            Map<String, Object> redisHealth = tuple.getT1();
            Map<String, Object> dbHealth = tuple.getT2();
            
            boolean isReady = "UP".equals(redisHealth.get("status")) &&
                             "UP".equals(dbHealth.get("status"));
            
            readiness.put("status", isReady ? "UP" : "DOWN");
            readiness.put("timestamp", LocalDateTime.now().toString());
            readiness.put("redis", redisHealth);
            readiness.put("database", dbHealth);
            
            return readiness;
        }).onErrorReturn(createErrorHealth("Readiness check failed"));
    }

    /**
     * 存活状态检查
     */
    @GetMapping("/health/liveness")
    public Mono<Map<String, Object>> liveness() {
        return checkMemoryHealth()
                .map(memoryHealth -> {
                    Map<String, Object> liveness = new HashMap<>();
                    boolean isAlive = "UP".equals(memoryHealth.get("status"));
                    
                    liveness.put("status", isAlive ? "UP" : "DOWN");
                    liveness.put("timestamp", LocalDateTime.now().toString());
                    liveness.put("memory", memoryHealth);
                    
                    return liveness;
                });
    }

    /**
     * 格式化字节数
     */
    private String formatBytes(long bytes) {
        if (bytes < 1024) return bytes + " B";
        int exp = (int) (Math.log(bytes) / Math.log(1024));
        String pre = "KMGTPE".charAt(exp - 1) + "";
        return String.format("%.1f %sB", bytes / Math.pow(1024, exp), pre);
    }

    /**
     * 获取系统运行时间
     */
    private String getUptime() {
        long uptime = java.lang.management.ManagementFactory.getRuntimeMXBean().getUptime();
        long seconds = uptime / 1000;
        long minutes = seconds / 60;
        long hours = minutes / 60;
        long days = hours / 24;
        
        return String.format("%d days, %d hours, %d minutes", 
                days, hours % 24, minutes % 60);
    }

    /**
     * 创建错误健康状态
     */
    private Map<String, Object> createErrorHealth(String message) {
        Map<String, Object> errorHealth = new HashMap<>();
        errorHealth.put("status", "DOWN");
        errorHealth.put("message", message);
        errorHealth.put("timestamp", LocalDateTime.now().toString());
        return errorHealth;
    }

    /**
     * 创建组件错误健康状态
     */
    private Map<String, Object> createComponentErrorHealth(String message) {
        Map<String, Object> componentHealth = new HashMap<>();
        componentHealth.put("status", "DOWN");
        componentHealth.put("message", message);
        return componentHealth;
    }
}